package com.zc.gateway.verticle;

import com.zc.common.annotations.RouteHandler;
import com.zc.common.annotations.RouteMapping;
import com.zc.common.annotations.RouteMethod;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CookieHandler;
import io.vertx.ext.web.handler.CorsHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.reflections.Reflections;
import org.springframework.context.ApplicationContext;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import static io.vertx.core.http.HttpHeaders.*;

/**
 * <pre>
 * @File：BaseVerticle
 * @Version：1.0.0
 * @Author：Neil.Zhou
 * @CreateDate：2016/9/23 16:22
 * @Modify：
 * @ModifyDate：2016/9/23
 * @Descript： Verticle 父类
 * </pre>
 */

public class HttpServerVerticle extends AbstractVerticle {
    private static final Logger log = LogManager.getLogger(HttpServerVerticle.class.getName());
    private static final Reflections reflections = new Reflections("com.zc.gateway");

    protected Router router;
    HttpServer server;
    private HttpRouter httpRouter;
    private static int port=8080;

    public HttpServerVerticle(final ApplicationContext context,Vertx vertx) {
        httpRouter=new HttpRouter(context,vertx);
    }

    @Override
    public void start(Future<Void> future) throws Exception {
        log.info("==============web start==============");
        super.start();
        HttpServerOptions options = new HttpServerOptions().setMaxWebsocketFrameSize(1000000).setPort(port);
        server = vertx.createHttpServer(options);
        server.requestHandler(createRouter()::accept);
        server.listen(result -> {
            if (result.succeeded()) {
                future.complete();
            } else {
                future.fail(result.cause());
            }
        });
    }

    @Override
    public void stop(Future<Void> future) {
        if (server == null) {
            future.complete();
            return;
        }
        server.close(result -> {
            if (result.failed()) {
                future.fail(result.cause());
            } else {
                future.complete();
            }
        });
    }

    private Router createRouter() {
        router = Router.router(vertx);
        router.route().handler(ctx -> {
            log.debug("Receives the request info;===>path:{}, uri:{}, method:{}, version:{}, Dev:{}",ctx.request().path(),ctx.request().absoluteURI(),ctx.request().method(), ctx.request().headers().get("version"),ctx.request().headers().get("Dev"));
            ctx.response().headers().add(CONTENT_TYPE, "application/json; charset=utf-8");
            ctx.response().headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
            ctx.response().headers().add(ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, OPTIONS, PUT, DELETE, HEAD");
            ctx.response().headers().add(ACCESS_CONTROL_ALLOW_HEADERS, "X-PINGOTHER, Origin,Content-Type, Accept, X-Requested-With,Dev,Version");
            ctx.response().headers().add(ACCESS_CONTROL_MAX_AGE, "1728000");
            ctx.next();
        });
        Set<HttpMethod> method = new HashSet<HttpMethod>();
        method.add(HttpMethod.GET);
        method.add(HttpMethod.POST);
        method.add(HttpMethod.OPTIONS);
        method.add(HttpMethod.PUT);
        method.add(HttpMethod.DELETE);
        method.add(HttpMethod.HEAD);

        router.route().handler(CorsHandler.create("*").allowedMethods(method));
        router.route().handler(CookieHandler.create());
        router.route().handler(BodyHandler.create());

        try {
            httpRouter.routerHandle(router);
            registerHandlers();
        } catch (Exception e) {
            e.printStackTrace();
            log.error("Manually Register Handler Fail，Error details："+e.getMessage());
        }
        return router;
    }

    private void registerHandlers() {
        log.debug("Register available request handlers...");
        Set<Class<?>> handlers = reflections.getTypesAnnotatedWith(RouteHandler.class);
        for (Class<?> handler : handlers) {
            try {
                registerNewHandler(handler);
            } catch (Exception e) {
                log.error("Error register {}", handler);
            }
        }
    }

    private void registerNewHandler(Class<?> handler) throws Exception {
        String root = "/api";
        if (handler.isAnnotationPresent(RouteHandler.class)) {
            RouteHandler routeHandler = handler.getAnnotation(RouteHandler.class);
            root =root+routeHandler.value();
        }
        Object instance = handler.newInstance();
        Method[] methods = handler.getMethods();

        for (Method method : methods) {
            if (method.isAnnotationPresent(RouteMapping.class)) {
                RouteMapping mapping = method.getAnnotation(RouteMapping.class);
                RouteMethod routeMethod = mapping.method();
                String routeUrl="";
                if(mapping.value().startsWith("/:")){
                    routeUrl=(method.getName()+mapping.value());
                }else{
                    routeUrl=(mapping.value().endsWith(method.getName())?mapping.value():(mapping.isCover()?mapping.value():mapping.value()+method.getName()));
                    if(routeUrl.startsWith("/")){
                        routeUrl=routeUrl.substring(1);
                    }
                }
                String url = root + "/" +routeUrl;
                Handler<RoutingContext> methodHandler = (Handler<RoutingContext>) method.invoke(instance);
                log.debug("Register New Handler -> {}:{}", routeMethod, url);
                //String url, String serviceName, String desc
                switch (routeMethod) {
                    case POST:
                        router.post(url).handler(methodHandler);
                        break;
                    case PUT:
                        router.put(url).handler(methodHandler);
                        break;
                    case DELETE:
                        router.delete(url).handler(methodHandler);
                        break;
                    case GET: // fall through
                    default:
                        router.get(url).handler(methodHandler);
                        break;
                }
            }
        }
    }
}
